home *** CD-ROM | disk | FTP | other *** search
/ Skunkware 98 / Skunkware 98.iso / osr5 / sco / scripts / address < prev    next >
Encoding:
AWK Script  |  1997-08-26  |  44.9 KB  |  1,202 lines

  1. #!/usr/local/bin/gawk -f
  2. # address: find lines with matching name field in an address file
  3. # gawk is used because it has IGNORECASE.
  4. # @(#) address.gawk 2.6.1 97/07/26
  5. # johnd h. dubois iii (john@armory.com) 90/05/30
  6. # 90/11/14 removed ksh-specific code
  7. # 91/07/06 Changed to understand new record format (uses awk now),
  8. #          changed syntax so that all normal args are components of 
  9. #          a single name and each name given must be matched as a word
  10. # 91/09/30 finished rewrite for new record format
  11. # 91/10/10 Added phone number dereferencing; made empty fields be
  12. #          translated into single blanks so they'll be preserved when
  13. #          split on one-or-more newlines (for sh & ksh, which have no 
  14. #          facility to split on single instances of a character); added -@
  15. # 91/11/11 changed to use [] to indicate an indirect reference,
  16. #          expanded indirect reference check/lookup to include fields 2..4
  17. #          (address, phone number, email address)
  18. # 91/11/13 Awk prog too big for XENIX cmd line arg; 
  19. #          separated into sh & awk files
  20. # 92/01/22 Added LIB to gawk line
  21. # 92/04/27 Converted to #!gawk script
  22. # 92/07/19 Converted to handle multiple numbers in phone number field
  23. # 92/10/16 minor bugfix
  24. # 93/05/02 Allow colons as separators in $ADDRESS
  25. # 93/09/02 Added -l option
  26. # 94/03/13 Cleaned up.  Added comments.  Print matching records at end.
  27. #          Worked around gawk bugs.
  28. # 94/04/23 Read .addrrc
  29. # 96/01/21 Use $UHOME/.addrrc and $UHOME/.address
  30. # 96/05/06 Let % and @ be used as abbreviations in ADDRESS path.
  31. #          Added p option.
  32. # 97/07/26 2.6.1 Added M option.
  33.  
  34. # todo: allow searching on fields other than the name field.
  35.  
  36. BEGIN {
  37.     Name = "address"
  38.     defPub = "/usr/local/public/address"
  39.     Usage = "Usage: " Name " [-dDehlM@] [-a<filename[:filename...]>] name ..."
  40.     rcFile = ".addrrc"
  41.     ARGC = Opts(Name,Usage,"a:del@DhpxM",1,"~/" rcFile ":$UHOME/" rcFile,
  42.     "ADDRESS,DEREFERENCE,PRINTREGEX,PRINTFILENAME,MATCHAT",1)
  43.     Debug = Options["x"]
  44.     if (ARGC == -1) {
  45.     print Name ": " OptErr
  46.     exit(1)
  47.     }
  48.     if ("h" in Options) {
  49.     printf \
  50. "%s: find lines with matching name field in address files.\n"\
  51. "%s\n"\
  52. "%s searches address files for a given name, and prints any records that\n"\
  53. "have a matching name field.  By default, any of the following that exist\n"\
  54. "are searched: the public address file %s, the file named\n"\
  55. ".address in the invoking user's home directory, and the file named\n"\
  56. ".address in the directory specified by the environment variable UHOME, if\n"\
  57. "it is set.\n"\
  58. "Name specification:\n"\
  59. "Matching is not case sensitive.  The search name will match a name field\n"\
  60. "if each part of the search name occurs in the same order as given and as a\n"\
  61. "separate word in the name field.  The search name will match a name field\n"\
  62. "even if other text occurs in the name field between the parts of the\n"\
  63. "search name.  The parts of the search name should be separated by\n"\
  64. "whitespace on the command line, and should be quoted if they contain\n"\
  65. "characters meaningful to the shell.  @ and : can be matched by either @ or\n"\
  66. ":.  Blank fields are printed as lines consisting of a single space.  If\n"\
  67. "multiple matching records are found, they are printed separated by lines\n"\
  68. "consisting solely of '+'.\n"\
  69. "Options:\n"\
  70. "Some of the following options can also be set by assigning values to\n"\
  71. "variables in a configuration file named %s, which is searched for in the\n"\
  72. "invoking user's home directory and in the directory specified by the\n"\
  73. "environment variable UHOME, if it is set (if both files exist, values set\n"\
  74. "in the former take precedence).  Variables are assigned to with the\n"\
  75. "syntax:  varname=value  or in the case of flags, by simply putting the\n"\
  76. "indicated variable name in the file without a value.  Variable names are\n"\
  77. "given in parentheses following the option descriptions.\n"\
  78. "-h: Print this help list.\n"\
  79. "-a<filename[:filename...]>: Search the named file(s) instead of the\n"\
  80. "    default file.  Multiple filenames may be given by separating them from\n"\
  81. "    each other with whitespace or colons.  If -a is not given, the value\n"\
  82. "    of the environment variable ADDRESS is used if set; and if that is not\n"\
  83. "    set, the value set in the configuration file is used.  If none of\n"\
  84. "    these are are set, the default files listed above are used.  If any\n"\
  85. "    files are explicitly named, the default files are not automatically\n"\
  86. "    included; if they should be searched as well, they should be named\n"\
  87. "    along with whatever other files are named.  The public address file\n"\
  88. "    may be abbreviated as '%%', and the default private files as '@'.\n"\
  89. "-d: Dereference indirect entries (those that are contained in brackets).\n"\
  90. "    The string contained in brackets ([]) is looked up.  The name field of\n"\
  91. "    another record must match it exactly.  The indirect entry, including\n"\
  92. "    the enclosing brackets, is replaced by the entire equivalent field of\n"\
  93. "    the referenced record.  One level of dereferencing is done.\n"\
  94. "    (DEREFERENCE)\n"\
  95. "-e: Print the regular expression used to search the name field to the\n"\
  96. "    standard output (PRINTREGEX).\n"\
  97. "-D: Print a description of the format of an address file.\n"\
  98. "-l: Print the names of the files each entry was found in (PRINTFILENAME).\n"\
  99. "-p: Use the public address file (%s) only; equivalent to -a%%\n"\
  100. "-M: Multiple pattern search.  By default, the arguments passed are searched\n"\
  101. "    for as a single pattern.  If -M is given, each argument is search for\n"\
  102. "    separately.  If a search pattern contains whitespace, it must be quoted\n"\
  103. "    so that the shell will pass it as a single argument.\n"\
  104. "-@: Require that any @ or : in the name field be matched by the pattern.\n"\
  105. "    (MATCHAT).\n",Name,Usage,Name,defPub,rcFile,defPub
  106.     exit(0)
  107.     }
  108.     if ("D" in Options) {
  109.     print \
  110. "Format of an address file: an address file consists of records separated\n"\
  111. "by delimiter lines consisting solely of a '+' character.  The address file\n"\
  112. "must also begin and end with lines consisting solely of a '+' character.\n"\
  113. "The '+' character may not occur anywhere in the file except as a delimiter\n"\
  114. "line.  Each line of the record is a field.  The fields are used as follows:\n"\
  115. "+\n"\
  116. "Name\n"\
  117. "Address\n"\
  118. "Phone number\n"\
  119. "Email address\n"\
  120. "Comment ...\n"\
  121. "+\n"\
  122. "Comment can continue onto multiple lines.  Unused fields can be left\n"\
  123. "blank; unused fields at the end of the record do not need to be given.  \n"\
  124. Name " only interprets the name and (possibly) phone number fields.\n"\
  125. "An indirect reference can be given by specifying in a field\n"\
  126. "[indirect-name]\n"\
  127. "where indirect-name is an exact match for the name field of another record.\n"\
  128. "Other text may be included on the same line as indirect-name.\n"\
  129. "This can be used to add an extension to a PBX number, etc."
  130.     exit(0)
  131.     }
  132.     if (ARGC < 2) {
  133.     print Usage > "/dev/stderr"
  134.     exit(1)
  135.     }
  136.     if ("p" in Options)
  137.     Options["a"] = "%"
  138.     GetFiles(Files,Options)
  139.     NumMatches = SearchFiles(ARGV,"e" in Options,Files,MatchingRecords,
  140.     FileFound,"M" in Options)
  141.     PrintFilename = "l" in Options
  142.     Dereference = "d" in Options
  143.     for (i = 1; i <= NumMatches; i++)
  144.     PrintRec(MatchingRecords[i],FileFound[i],PrintFilename,Dereference)
  145. }
  146.  
  147. function FileExists(File, ret) {
  148.     ret = (File == "") || ((getline < File) == 1)
  149.     close(File)
  150.     return ret
  151. }
  152.  
  153. function GetPubFile(Files,nFiles,iFiles,  file) {
  154.     file = defPub
  155.     if (!(file in iFiles) && FileExists(file))
  156.     iFiles[Files[++nFiles] = file]
  157.     return nFiles
  158. }
  159.  
  160. function GetPrivFiles(Files,nFiles,iFiles,  file) {
  161.     if ("HOME" in ENVIRON && \
  162.     !((file = ENVIRON["HOME"] "/.address") in iFiles) && FileExists(file))
  163.     iFiles[Files[++nFiles] = file]
  164.     if ("UHOME" in ENVIRON && \
  165.     !((file = ENVIRON["UHOME"] "/.address") in iFiles) && FileExists(file))
  166.     iFiles[Files[++nFiles] = file]
  167.     return nFiles
  168. }
  169.  
  170. function GetFiles(Files,Options,  i,nFiles,aFiles,file,iFiles) {
  171.     nFiles = 0
  172.     if ("a" in Options) {
  173.     split(Options["a"],aFiles,"[ \t:]+")
  174.     for (i = 1; i in aFiles; i++) {
  175.         if ((file = aFiles[i]) == "" || file in iFiles)
  176.         continue
  177.         iFiles[i]
  178.         if (file == "%")
  179.         nFiles = GetPubFile(Files,nFiles,iFiles)
  180.         else if (file == "@")
  181.         nFiles = GetPrivFiles(Files,nFiles,iFiles)
  182.         else
  183.         Files[++nFiles] = file
  184.     }
  185.     }
  186.     else {
  187.     # If no files specified on command line and $ADDRESS not set,
  188.     # use the default files (but only those that exist)
  189.     nFiles = GetPubFile(Files,nFiles,iFiles)
  190.     nFiles = GetPrivFiles(Files,nFiles,iFiles)
  191.     if (!nFiles) {
  192.         print Name ": No address files." > "/dev/stderr"
  193.         exit(1)
  194.     }
  195.     }
  196.     if (Debug) {
  197.     printf "%d address files:\n",nFiles > "/dev/stderr"
  198.     for (i = 1; i <= nFiles; i++)
  199.         printf "%s ",Files[i] > "/dev/stderr"
  200.     print "" > "/dev/stderr"
  201.     }
  202. }
  203.  
  204. # Returns the number of matching records
  205. function SearchFiles(NamePieces,PrintExpression,Files,
  206. MatchingRecords,FileFound,Multiple,
  207. Name,i,count) {
  208.     if (Debug) {
  209.     print "Arguments are:" > "/dev/stderr"
  210.     for (i = 1; i in NamePieces; i++)
  211.         printf "%d:%s\n",i,NamePieces[i] > "/dev/stderr"
  212.     }
  213.     if (Multiple) {
  214.     for (i = 1; i in NamePieces; i++)
  215.         count += Search(\
  216.         NamePieces[i],PrintExpression,Files,MatchingRecords,FileFound)
  217.     return count
  218.     }
  219.     else {
  220.     for (i = 1; i in NamePieces; i++)
  221.         Name = Name " " NamePieces[i]
  222.     Name = substr(Name,2)
  223.     return Search(Name,PrintExpression,Files,MatchingRecords,FileFound)
  224.     }
  225. }
  226.  
  227. function Search(Name,PrintExpression,Files,MatchingRecords,FileFound,
  228. RName,Qualifier,FirstName) {
  229.     if (Debug)
  230.     printf "Searching for pattern \"%s\".\n",Name > "/dev/stderr"
  231.     RName = MakePats(Name,RetVals)
  232.     Qualifier = RetVals["Qualifier"]
  233.     FirstName = RetVals["FirstName"]
  234.     if (PrintExpression) {
  235.     print "Name pattern: " RName > "/dev/stderr"
  236.     if (Qualifier != "")
  237.         print "Qualifier: " Qualifier > "/dev/stderr"
  238.     }
  239.     return FindAddr(Files,FirstName,RName,Qualifier,MatchingRecords,FileFound)
  240. }
  241.  
  242. function Setup() {
  243.     RS = "+"
  244.     OFS = FS = "\n"
  245.     IGNORECASE = 1
  246.  
  247.     AN = "[a-z0-9]"        # alphanum
  248.     NAN = "[^ \ta-z0-9" a "]"    # nonalphanum
  249.     NotAN = "[^a-z0-9" a "]"    # not-alphanum
  250.     NotNAN = "[ \ta-z0-9]"    # not-nonalphanum
  251.  
  252.     # The type of a word is (Word ~ AN)
  253.     an = 1
  254.     nan = 0
  255.  
  256.     # Delimiters for use at ends of line
  257.     Delim[an] = NotAN
  258.     Delim[nan] = NotNAN
  259.     # Delimiters between two words
  260.     Delim[an,an] = NotAN "(.*" NotAN ")?"
  261.     Delim[an,nan] = "(" NotAN ".*" NotNAN "| )?"
  262.     Delim[nan,an] =  "(" NotNAN ".*" NotAN "| )?"
  263.     Delim[nan,nan] =  NotNAN "(.*" NotNAN ")?"
  264. }
  265.  
  266. # Convert Name to a search pattern.
  267. # Returns pattern qualifier in RetVals["Qualifier"],
  268. # first name in RetVals["FirstName"].
  269. # Return value: Search pattern.
  270. function MakePats(Name,RetVals) {
  271.     Setup()
  272.     if (Name ~ "[@:]") {
  273.     # Split the last [@:]-separated part off into Qualifier
  274.     Qualifier = Name
  275.     sub(".*[@:]","",Qualifier)
  276.     sub("[@:][^@:]*$","",Name)
  277.     gsub("[@:]","[@:]",Name)    # make either : or @ match either : or @
  278.     RetVals["Qualifier"] = Name2SearchPat(Qualifier)
  279.     }
  280.     else
  281.     delete RetVals["Qualifier"]
  282.     return Name2SearchPat(Name,RetVals)
  283. }
  284.  
  285. # Uses global: Delim[]
  286. # Returns first name in pattern in RetVals["FirstName"]
  287. # Return value: Search pattern
  288. function Name2SearchPat(Name,RetVals,  Words,Types,NumWords,i,Pat) {
  289.     # Tell awk that Words and Types are arrays...
  290.     Words[1] = Types[1] = ""
  291.     NumWords = GetWords(Name,Words,Types)
  292.     if (!NumWords)    # Quit if no words given, just whitespace
  293.     exit 1
  294.     for (i = 1; i < NumWords; i++)
  295.     Pat = Pat Words[i] Delim[Types[i],Types[i+1]]
  296.     Pat = Delim[Types[1]] ")" Pat Words[NumWords] "(" Delim[Types[NumWords]]
  297.     Pat = "(^|" Pat "|$)"
  298.     RetVals["FirstName"] = Words[1]
  299.     return Pat
  300. }
  301.  
  302. # A word is a sequence of either alphanums or non-alphanums,
  303. # as specified by the sets AN and NAN (neither of which include whitespace).
  304. # Words are delimited by whitespace or by the juxtaposition of AN and NAN.
  305. # GetWords puts the words of S in Words and the type of each word in Types.
  306. # The return value is the number of words found.
  307. function GetWords(S,Words,Types, i,Pos) {
  308.     # convert all whitespace to single spaces
  309.     gsub("[ \t]+"," ",S)
  310.     # get rid of trailing whitespace
  311.     gsub(" $","",S)
  312.     i = 0
  313.     while (S != "") {
  314.     sub("^ ","",S)
  315.     Pos = match(S,". |" NAN AN "|" AN NAN)
  316.     if (Pos) {
  317.         Words[++i] = substr(S,1,Pos)
  318.         Types[i] = S ~ "^" AN
  319.         S = substr(S,Pos + 1)
  320.     }
  321.     else {
  322.         Words[++i] = S
  323.         Types[i] = S ~ "^" AN
  324.         S = ""
  325.     }
  326.     }
  327.     return i
  328. }
  329.  
  330. # Searches for an exact complete match of Name to the name field of
  331. # an entry in the address files
  332. # Returns the first matching record, or null if none found.
  333. # On success, if the file that the maching record was found is not an index
  334. # of IndFiles[], its name is printed and it is made an index of IndFiles[].
  335. function GetEntry(Name,PrintFilename,IndFiles,
  336. FileInd,File,Entry,ret,IndFile) {
  337.     for (FileInd = 1; FileInd in Files; FileInd++) {
  338.     File =  Files[FileInd]
  339.     # Make filename different
  340.     # so that the file being read by FindAddr won't be affected
  341.     # Don't have to do this with records not printed until end
  342.     #if (File ~ "^/")
  343.     #    File = "/" File
  344.     #else
  345.     #    File = "./" File
  346.     if (Debug)
  347.         printf "Checking file \"%s\" for name \"%s\"...\n",
  348.         File,Name > "/dev/stderr"
  349.     while ((ret = (getline Entry < File)) == 1)
  350.         if (Entry ~ "^\n" Name "\n") {
  351.         close(File)
  352.         if (Debug)
  353.             printf "Found entry: %s\n",Entry > "/dev/stderr"
  354.         sub("^\n","",Entry)
  355.         IndFile = Files[FileInd]
  356.         if (PrintFilename && !(IndFile in IndFiles)) {
  357.             print IndFile
  358.             IndFiles[IndFile]
  359.         }
  360.         return Entry
  361.         }
  362.     if (ret == -1)
  363.         printf "Could not open address file \"%s\".\n",Files[FileInd] > \
  364.         "/dev/stderr"
  365.     close(File)
  366.     }
  367.     return ""
  368. }
  369.  
  370. # Sets MatchingRecords[n..m] to matching records, FileFound[n..m] to the file
  371. # each match is found in, and returns the number of matching records.
  372. # n is the starting index.  If MatchingRecords["start"] is not set, n is 1;
  373. # if it is set, n is set to its value+1 and it is updated to the last index
  374. # used.
  375. function FindAddr(Files,FirstName,RName,Qualifier,MatchingRecords,FileFound,
  376. Ext,i,ret,InFile,NumMatch,MatchInd) {
  377.     if ("start" in MatchingRecords)
  378.     MatchInd = MatchingRecords["start"]
  379.     for (i = 1; i in Files; i++) {
  380.     InFile = Files[i]
  381.     while ((ret = (getline < InFile)) == 1) {
  382.         # $1 is always empty (it is the field between the + and the 
  383.         # first newline).  
  384.         # Compare against FirstName first to avoid expensive 
  385.         # comparision with RName for most records.
  386.         if ( \
  387.         ($2 ~ FirstName) && ($2 ~ RName) \
  388.         &&
  389.         # If +@ was given, either a qualifer must be given in
  390.         # the search pattern...
  391.         ( \
  392.         !("@" in Options) || (Qualifier != "") ||
  393.         # ... or the name field must not have a qualifier and the first
  394.         # number on the phone number field must not have a qualifier
  395.         (($2 !~ "[@:]") && ($4 ~ "^(\\[|[^: ]+(  |$))" )) \
  396.         ) &&
  397.         # If a qualifier is given in the search pattern,
  398.         ((Qualifier == "") ||
  399.         # it must match a qualifier in the name field...
  400.         match($2,".*[@:]") && (substr($2,1,RLENGTH - 1) ~ RName) &&
  401.         (substr($2,RLENGTH + 1) ~ Qualifier) ||
  402.         # ... or a qualifier in the phone number field
  403.         (($4 = PhoneMatch($4,Qualifier)) != "")) ) {
  404.         MatchingRecords[++MatchInd] = $0
  405.         FileFound[MatchInd] = InFile
  406.         NumMatch++
  407.         if (Debug)
  408.             printf "Found match:\n%s\n",$0 > "/dev/stderr"
  409.         }
  410.     }
  411.     if (ret == -1) {
  412.         printf "Error reading file \"%s\".\n",InFile > "/dev/stderr"
  413.         exit(1)
  414.     }
  415.     close(InFile)
  416.     }
  417.     MatchingRecords["start"] = MatchInd
  418.     return NumMatch
  419. }
  420.  
  421. function PhoneMatch(Line,Qualifier,  i,j,Fields) {
  422.     split(Line,Fields,"  +")
  423.     for (i = 1; i in Fields; i++)
  424.     if (match(Fields[i],".*:") && 
  425.     (substr(Fields[i],1,RLENGTH - 1) ~ Qualifier)) {
  426.         Line = Fields[i]
  427.         for (j = 1; j in Fields; j++)
  428.         if (j != i)
  429.             Line = Line "  " Fields[j]
  430.         return Line
  431.     }
  432.     return ""
  433. }
  434.  
  435. # Uses/sets global _Plus, to arrange for each pair of records to be separated
  436. # by a + character alone on a line.
  437. function PrintRec(Record,InFile,PrintFilename,Dereference,  FieldVal,
  438. NumFields,MainFields,FieldNum,IndirName,Fields,Indir,i,IndFiles,Entry) {
  439.     # Convince gawk that Fields is an array...
  440.     Fields["x"] = ""
  441.     # Print a "+" between records, but not before the first record
  442.     printf _Plus
  443.     _Plus = "+\n"
  444.     # Print source file if asked for
  445.     if (PrintFilename) {
  446.     print InFile
  447.     IndFiles[InFile]
  448.     }
  449.     # Get rid of the leading & trailing newlines
  450.     NumFields = split(substr(Record,2),MainFields,"\n")
  451.     if (MainFields[NumFields] == "")
  452.     NumFields--
  453.     # Dereference fields 2-4 (fields which do not exist will fail to match)
  454.     for (FieldNum = 2; FieldNum <= 4; FieldNum++) {
  455.     FieldVal = MainFields[FieldNum]
  456.     # A reference looks like: [replacement-record-name-field] other-stuff,
  457.     # where replacement-record-name-field is an exact match for the name
  458.     # field of another record.
  459.     # Find the reference, if any.
  460.     # gawk core dumps on this when the pattern is given in //, but not
  461.     # when given in quotes...
  462.     if (match(FieldVal,"\\[[^]]+\\]")) {
  463.         IndirName = substr(MainFields[FieldNum],RSTART + 1,RLENGTH - 2)
  464.         if ((Entry = \
  465.         GetEntry(IndirName,PrintFilename,IndFiles)) == "") {
  466.         printf \
  467. "Indirect reference not found for \"%s\"\n"\
  468. "in field %d of the following entry:\n%s\n",IndirName,FieldNum,substr(Record,2)
  469.         exit 1
  470.         }
  471.         split(Entry,Fields,"\n")
  472.         Indir = Fields[FieldNum]
  473.         # Replace indirect reference with dereferenced value
  474.         if (!Dereference)
  475.         Indir = "[" IndirName "]->[" Indir "]"
  476.         MainFields[FieldNum] = \
  477.         substr(MainFields[FieldNum],1,RSTART -1) Indir \
  478.         substr(MainFields[FieldNum],RSTART + RLENGTH)
  479.     }
  480.     }
  481.     for (i = 1; i <= NumFields; i++)
  482.     print MainFields[i]
  483. }
  484.  
  485. ### Start of ProcArgs library
  486. # @(#) ProcArgs 1.12 97/05/26
  487. # 92/02/29 john h. dubois iii (john@armory.com)
  488. # 93/07/18 Added "#" arg type
  489. # 93/09/26 Do not count h option against MinArgs
  490. # 94/01/01 Stop scanning at first non-option arg.  Added ">" option type.
  491. #          Removed meaning of "+" or "-" by itself.
  492. # 94/03/08 Added & option and *()< option types.
  493. # 94/04/02 Added NoRCopt to Opts()
  494. # 94/06/11 Mark numeric variables as such.
  495. # 94/07/08 Opts(): Do not require any args if h option is given.
  496. # 95/01/22 Record options given more than once.  Record option num in argv.
  497. # 95/06/08 Added ExclusiveOptions().
  498. # 96/01/20 Let rcfiles be a colon-separated list of filenames.
  499. #          Expand $VARNAME at the start of its filenames.
  500. #          Let varname=0 and [-+]option- turn off an option.
  501. # 96/05/05 Changed meaning of 7th arg to Opts; now can specify exactly how many
  502. #          of the vars should be searched for in the environment.
  503. #          Check for duplicate rcfiles.
  504. # 96/05/13 Return more specific error values.  Note: ProcArgs() and InitOpts()
  505. #          now return various negatives values on error, not just -1, and
  506. #          Opts() may set Err to various positive values, not just 1.
  507. #          Added AllowUnrecOpt.
  508. # 96/05/23 Check type given for & option
  509. # 96/06/15 Re-port to awk
  510. # 96/10/01 Moved file-reading code into ReadConfFile(), so that it can be
  511. #          used by other functions.
  512. # 96/10/15 Added OptChars
  513. # 96/11/01 Added exOpts arg to Opts()
  514. # 96/11/16 Added ; type
  515. # 96/12/08 Added Opt2Set() & Opt2Sets()
  516. # 96/12/27 Added CmdLineOpt()
  517. # 97/02/22 Remove packed elements.
  518. # 97/02/28 Make sequence # for rcfiles & environ be "f" and "e".
  519. #          Replaced CmdLineOpt() with OptsGiven().
  520. # 97/05/26 Added mangleHelp().
  521.  
  522. # optlist is a string which contains all of the possible command line options.
  523. # A character followed by certain characters indicates that the option takes
  524. # an argument, with type as follows:
  525. # :    String argument
  526. # ;    Non-empty string argument
  527. # *    Floating point argument
  528. # (    Non-negative floating point argument
  529. # )    Positive floating point argument
  530. # #    Integer argument
  531. # <    Non-negative integer argument
  532. # >    Positive integer argument
  533. # The only difference the type of argument makes is in the runtime argument
  534. # error checking that is done.
  535.  
  536. # The & option is a special case used to get numeric options without the
  537. # user having to give an option character.  It is shorthand for [-+.0-9].
  538. # If & is included in optlist and an option string that begins with one of
  539. # these characters is seen, the value given to "&" will include the first
  540. # char of the option.  & must be followed by a type character other than ":"
  541. # or ";".
  542. # Note that if e.g. &> is given, an option of -.5 will produce an error.
  543.  
  544. # Strings in argv[] which begin with "-" or "+" are taken to be
  545. # strings of options, except that a string which consists solely of "-"
  546. # or "+" is taken to be a non-option string; like other non-option strings,
  547. # it stops the scanning of argv and is left in argv[].
  548. # An argument of "--" or "++" also stops the scanning of argv[] but is removed.
  549. # If an option takes an argument, the argument may either immediately
  550. # follow it or be given separately.
  551. # "-" and "+" options are treated the same.  "+" is allowed because most awks
  552. # take any -options to be arguments to themselves.  gawk 2.15 was enhanced to
  553. # stop scanning when it encounters an unrecognized option, though until 2.15.5
  554. # this feature had a flaw that caused problems in some cases.  See the OptChars
  555. # parameter to explicitly set the option-specifier characters.
  556.  
  557. # If an option that does not take an argument is given,
  558. # an index with its name is created in Options and its value is set to the
  559. # number of times it occurs in argv[].
  560.  
  561. # If an option that does take an argument is given, an index with its name is
  562. # created in Options and its value is set to the value of the argument given
  563. # for it, and Options[option-name,"count"] is (initially) set to the 1.
  564. # If an option that takes an argument is given more than once,
  565. # Options[option-name,"count"] is incremented, and the value is assigned to
  566. # the index (option-name,instance) where instance is 2 for the second occurance
  567. # of the option, etc.
  568. # In other words, the first time an option with a value is encountered, the
  569. # value is assigned to an index consisting only of its name; for any further
  570. # occurances of the option, the value index has an extra (count) dimension.
  571.  
  572. # The sequence number for each option found in argv[] is stored in
  573. # Options[option-name,"num",instance], where instance is 1 for the first
  574. # occurance of the option, etc.  The sequence number starts at 1 and is
  575. # incremented for each option, both those that have a value and those that
  576. # do not.  Options set from a config file get a sequence number of "f", and
  577. # options set in the environment get a sequence number of "e".
  578.  
  579. # Options and their arguments are deleted from argv.
  580. # Note that this means that there may be gaps left in the indices of argv[].
  581. # If compress is nonzero, argv[] is packed by moving its elements so that
  582. # they have contiguous integer indices starting with 0.
  583. # Option processing will stop with the first unrecognized option, just as
  584. # though -- or ++ was given except that the unrecognized option will not be
  585. # removed from ARGV[].  Normally, an error value is returned in this case.
  586. # If AllowUnrecOpt is true, it is not an error for an unrecognized option to
  587. # be found, so the number of remaining arguments is returned instead.
  588. # If OptChars is not a null string, it is the set of characters that indicate
  589. # that an argument is an option string if the string begins with one of the
  590. # characters.  A string consisting solely of two of the same option-indicator
  591. # characters stops the scanning of argv[].  The default is "-+".
  592. # argv[0] is not examined.
  593. # The number of arguments left in argc is returned.
  594. # If an error occurs, the global string OptErr is set to an error message
  595. # and a negative value is returned.
  596. # Current error values:
  597. # -1: option that required an argument did not get it.
  598. # -2: argument of incorrect type supplied for an option.
  599. # -3: unrecognized (invalid) option.
  600. function ProcArgs(argc,argv,OptList,Options,compress,AllowUnrecOpt,OptChars,
  601. ArgNum,ArgsLeft,Arg,ArgLen,ArgInd,Option,Pos,NumOpt,Value,HadValue,specGiven,
  602. NeedNextOpt,GotValue,OptionNum,Escape,dest,src,count,c,OptTerm,OptCharSet)
  603. {
  604. # ArgNum is the index of the argument being processed.
  605. # ArgsLeft is the number of arguments left in argv.
  606. # Arg is the argument being processed.
  607. # ArgLen is the length of the argument being processed.
  608. # ArgInd is the position of the character in Arg being processed.
  609. # Option is the character in Arg being processed.
  610. # Pos is the position in OptList of the option being processed.
  611. # NumOpt is true if a numeric option may be given.
  612.     ArgsLeft = argc
  613.     NumOpt = index(OptList,"&")
  614.     OptionNum = 0
  615.     if (OptChars == "")
  616.     OptChars = "-+"
  617.     while (OptChars != "") {
  618.     c = substr(OptChars,1,1)
  619.     OptChars = substr(OptChars,2)
  620.     OptCharSet[c]
  621.     OptTerm[c c]
  622.     }
  623.     for (ArgNum = 1; ArgNum < argc; ArgNum++) {
  624.     Arg = argv[ArgNum]
  625.     if (length(Arg) < 2 || !((specGiven = substr(Arg,1,1)) in OptCharSet))
  626.         break    # Not an option; quit
  627.     if (Arg in OptTerm) {
  628.         delete argv[ArgNum]
  629.         ArgsLeft--
  630.         break
  631.     }
  632.     ArgLen = length(Arg)
  633.     for (ArgInd = 2; ArgInd <= ArgLen; ArgInd++) {
  634.         Option = substr(Arg,ArgInd,1)
  635.         if (NumOpt && Option ~ /[-+.0-9]/) {
  636.         # If this option is a numeric option, make its flag be & and
  637.         # its option string flag position be the position of & in
  638.         # the option string.
  639.         Option = "&"
  640.         Pos = NumOpt
  641.         # Prefix Arg with a char so that ArgInd will point to the
  642.         # first char of the numeric option.
  643.         Arg = "&" Arg
  644.         ArgLen++
  645.         }
  646.         # Find position of flag in option string, to get its type (if any).
  647.         # Disallow & as literal flag.
  648.         else if (!(Pos = index(OptList,Option)) || Option == "&") {
  649.         if (AllowUnrecOpt) {
  650.             Escape = 1
  651.             break
  652.         }
  653.         else {
  654.             OptErr = "Invalid option: " specGiven Option
  655.             return -3
  656.         }
  657.         }
  658.  
  659.         # Find what the value of the option will be if it takes one.
  660.         # NeedNextOpt is true if the option specifier is the last char of
  661.         # this arg, which means that if the option requires a value it is
  662.         # the next arg.
  663.         if (NeedNextOpt = (ArgInd >= ArgLen)) { # Value is the next arg
  664.         if (GotValue = ArgNum + 1 < argc)
  665.             Value = argv[ArgNum+1]
  666.         }
  667.         else {    # Value is included with option
  668.         Value = substr(Arg,ArgInd + 1)
  669.         GotValue = 1
  670.         }
  671.  
  672.         if (HadValue = AssignVal(Option,Value,Options,
  673.         substr(OptList,Pos + 1,1),GotValue,"",++OptionNum,!NeedNextOpt,
  674.         specGiven)) {
  675.         if (HadValue < 0)    # error occured
  676.             return HadValue
  677.         if (HadValue == 2)
  678.             ArgInd++    # Account for the single-char value we used.
  679.         else {
  680.             if (NeedNextOpt) {    # option took next arg as value
  681.             delete argv[++ArgNum]
  682.             ArgsLeft--
  683.             }
  684.             break    # This option has been used up
  685.         }
  686.         }
  687.     }
  688.     if (Escape)
  689.         break
  690.     # Do not delete arg until after processing of it, so that if it is not
  691.     # recognized it can be left in ARGV[].
  692.     delete argv[ArgNum]
  693.     ArgsLeft--
  694.     }
  695.     if (compress != 0) {
  696.     dest = 1
  697.     src = argc - ArgsLeft + 1
  698.     if (src != dest) {
  699.         for (count = ArgsLeft - 1; count; count--) {
  700.         ARGV[dest] = ARGV[src]
  701.         dest++
  702.         src++
  703.         }
  704.         for (; dest < src; dest++)
  705.         delete ARGV[dest]
  706.     }
  707.     }
  708.     return ArgsLeft
  709. }
  710.  
  711. # Assignment to values in Options[] occurs only in this function.
  712. # Option: Option specifier character.
  713. # Value: Value to be assigned to option, if it takes a value.
  714. # Options[]: Options array to return values in.
  715. # ArgType: Argument type specifier character.
  716. # GotValue: Whether any value is available to be assigned to this option.
  717. # Name: Name of option being processed.
  718. # OptionNum: Number of this option (starting with 1) if set in argv[],
  719. #     or 0 if it was given in a config file or in the environment.
  720. # SingleOpt: true if the value (if any) that is available for this option was
  721. #     given as part of the same command line arg as the option.  Used only for
  722. #     options from the command line.
  723. # specGiven is the option specifier character use, if any (e.g. - or +),
  724. # for use in error messages.
  725. # Global variables: OptErr
  726. # Return value: negative value on error, 0 if option did not require an
  727. # argument, 1 if it did & used the whole arg, 2 if it required just one char of
  728. # the arg.
  729. # Current error values:
  730. # -1: Option that required an argument did not get it.
  731. # -2: Value of incorrect type supplied for option.
  732. # -3: Bad type given for option &
  733. function AssignVal(Option,Value,Options,ArgType,GotValue,Name,OptionNum,
  734. SingleOpt,specGiven,  UsedValue,Err,NumTypes) {
  735.     # If option takes a value...    [
  736.     NumTypes = "*()#<>]"
  737.     if (Option == "&" && ArgType !~ "[" NumTypes) {    # ]
  738.     OptErr = "Bad type given for & option"
  739.     return -3
  740.     }
  741.  
  742.     if (UsedValue = (ArgType ~ "[:;" NumTypes)) {    # ]
  743.     if (!GotValue) {
  744.         if (Name != "")
  745.         OptErr = "Variable requires a value -- " Name
  746.         else
  747.         OptErr = "option requires an argument -- " Option
  748.         return -1
  749.     }
  750.     if ((Err = CheckType(ArgType,Value,Option,Name,specGiven)) != "") {
  751.         OptErr = Err
  752.         return -2
  753.     }
  754.     # Mark this as a numeric variable; will be propogated to Options[] val.
  755.     if (ArgType != ":" && ArgType != ";")
  756.         Value += 0
  757.     if ((Instance = ++Options[Option,"count"]) > 1)
  758.         Options[Option,Instance] = Value
  759.     else
  760.         Options[Option] = Value
  761.     }
  762.     # If this is an environ or rcfile assignment & it was given a value...
  763.     else if (!OptionNum && Value != "") {
  764.     UsedValue = 1
  765.     # If the value is "0" or "-" and this is the first instance of it,
  766.     # do not set Options[Option]; this allows an assignment in an rcfile to
  767.     # turn off an option (for the simple "Option in Options" test) in such
  768.     # a way that it cannot be turned on in a later file.
  769.     if (!(Option in Options) && (Value == "0" || Value == "-"))
  770.         Instance = 1
  771.     else
  772.         Instance = ++Options[Option]
  773.     # Save the value even though this is a flag
  774.     Options[Option,Instance] = Value
  775.     }
  776.     # If this is a command line flag and has a - following it in the same arg,
  777.     # it is being turned off.
  778.     else if (OptionNum && SingleOpt && substr(Value,1,1) == "-") {
  779.     UsedValue = 2
  780.     if (Option in Options)
  781.         Instance = ++Options[Option]
  782.     else
  783.         Instance = 1
  784.     Options[Option,Instance]
  785.     }
  786.     # If this is a flag assignment without a value, increment the count for the
  787.     # flag unless it was turned off.  The indicator for a flag being turned off
  788.     # is that the flag index has not been set in Options[] but it has an
  789.     # instance count.
  790.     else if (Option in Options || !((Option,1) in Options))
  791.     # Increment number of times this flag seen; will inc null value to 1
  792.     Instance = ++Options[Option]
  793.     Options[Option,"num",Instance] = OptionNum
  794.     return UsedValue
  795. }
  796.  
  797. # Option is the option letter
  798. # Value is the value being assigned
  799. # Name is the var name of the option, if any
  800. # ArgType is one of:
  801. # :    String argument
  802. # ;    Non-null string argument
  803. # *    Floating point argument
  804. # (    Non-negative floating point argument
  805. # )    Positive floating point argument
  806. # #    Integer argument
  807. # <    Non-negative integer argument
  808. # >    Positive integer argument
  809. # specGiven is the option specifier character use, if any (e.g. - or +),
  810. # for use in error messages.
  811. # Returns null on success, err string on error
  812. function CheckType(ArgType,Value,Option,Name,specGiven,  Err,ErrStr) {
  813.     if (ArgType == ":")
  814.     return ""
  815.     if (ArgType == ";") {
  816.     if (Value == "")
  817.         Err = "must be a non-empty string"
  818.     }
  819.     # A number begins with optional + or -, and is followed by a string of
  820.     # digits or a decimal with digits before it, after it, or both
  821.     else if (Value !~ /^[-+]?([0-9]+|[0-9]*\.[0-9]+|[0-9]+\.)$/)
  822.     Err = "must be a number"
  823.     else if (ArgType ~ "[#<>]" && Value ~ /\./)
  824.     Err = "may not include a fraction"
  825.     else if (ArgType ~ "[()<>]" && Value < 0)
  826.     Err = "may not be negative"
  827.     # (
  828.     else if (ArgType ~ "[)>]" && Value == 0)
  829.     Err = "must be a positive number"
  830.     if (Err != "") {
  831.     ErrStr = "Bad value \"" Value "\".  Value assigned to "
  832.     if (Name != "")
  833.         return ErrStr "variable " substr(Name,1,1) " " Err
  834.     else {
  835.         if (Option == "&")
  836.         Option = Value
  837.         return ErrStr "option " specGiven substr(Option,1,1) " " Err
  838.     }
  839.     }
  840.     else
  841.     return ""
  842. }
  843.  
  844. # Note: only the above functions are needed by ProcArgs.
  845. # The rest of these functions call ProcArgs() and also do other
  846. # option-processing stuff.
  847.  
  848. # Opts: Process command line arguments.
  849. # Opts processes command line arguments using ProcArgs()
  850. # and checks for errors.  If an error occurs, a message is printed
  851. # and the program is exited.
  852. #
  853. # Input variables:
  854. # Name is the name of the program, for error messages.
  855. # Usage is a usage message, for error messages.
  856. # OptList the option description string, as used by ProcArgs().
  857. # MinArgs is the minimum number of non-option arguments that this
  858. # program should have, non including ARGV[0] and +h.
  859. # If the program does not require any non-option arguments,
  860. # MinArgs should be omitted or given as 0.
  861. # rcFiles, if given, is a colon-seprated list of filenames to read for
  862. # variable initialization.  If a filename begins with ~/, the ~ is replaced
  863. # by the value of the environment variable HOME.  If a filename begins with
  864. # $, the part from the character after the $ up until (but not including)
  865. # the first character not in [a-zA-Z0-9_] will be searched for in the
  866. # environment; if found its value will be substituted, if not the filename will
  867. # be discarded.
  868. # rcfiles are read in the order given.
  869. # Values given in them will not override values given on the command line,
  870. # and values given in later files will not override those set in earlier
  871. # files, because AssignVal() will store each with a different instance index.
  872. # The first instance of each variable, either on the command line or in an
  873. # rcfile, will be stored with no instance index, and this is the value
  874. # normally used by programs that call this function.
  875. # VarNames is a comma-separated list of variable names to map to options,
  876. # in the same order as the options are given in OptList.
  877. # If EnvSearch is given and nonzero, the first EnvSearch variables will also be
  878. # searched for in the environment.  If set to -1, all values will be searched
  879. # for in the environment.  Values given in the environment will override
  880. # those given in the rcfiles but not those given on the command line.
  881. # NoRCopt, if given, is an additional letter option that if given on the
  882. # command line prevents the rcfiles and environment from being read.
  883. # See ProcArgs() for a description of AllowUnRecOpt and optChars, and
  884. # ExclusiveOptions() for a description of exOpts.
  885. # Special options:
  886. # If x is made an option and is given, some debugging info is output.
  887. # h is assumed to be the help option.
  888.  
  889. # Global variables:
  890. # The command line arguments are taken from ARGV[].
  891. # The arguments that are option specifiers and values are removed from
  892. # ARGV[], leaving only ARGV[0] and the non-option arguments.
  893. # The number of elements in ARGV[] should be in ARGC.
  894. # After processing, ARGC is set to the number of elements left in ARGV[].
  895. # The option values are put in Options[].
  896. # On error, Err is set to a positive integer value so it can be checked for in
  897. # an END block.
  898. # Return value: The number of elements left in ARGV is returned.
  899. # Must keep OptErr global since it may be set by InitOpts().
  900. function Opts(Name,Usage,OptList,MinArgs,rcFiles,VarNames,EnvSearch,NoRCopt,
  901. AllowUnrecOpt,optChars,exOpts,  ArgsLeft,e) {
  902.     if (MinArgs == "")
  903.     MinArgs = 0
  904.     ArgsLeft = ProcArgs(ARGC,ARGV,OptList NoRCopt,Options,1,AllowUnrecOpt,
  905.     optChars)
  906.     if (ArgsLeft < (MinArgs+1) && !("h" in Options)) {
  907.     if (ArgsLeft >= 0) {
  908.         OptErr = "Not enough arguments"
  909.         Err = 4
  910.     }
  911.     else
  912.         Err = -ArgsLeft
  913.     print mangleHelp(sprintf("%s: %s.\nUse -h for help.\n%s",
  914.     Name,OptErr,Usage)," \t\n[") > "/dev/stderr"
  915.     exit 1
  916.     }
  917.     if (rcFiles != "" && (NoRCopt == "" || !(NoRCopt in Options)) &&
  918.     (e = InitOpts(rcFiles,Options,OptList,VarNames,EnvSearch)) < 0)
  919.     {
  920.     print Name ": " OptErr ".\nUse -h for help." > "/dev/stderr"
  921.     Err = -e
  922.     exit 1
  923.     }
  924.     if ((exOpts != "") && ((OptErr = ExclusiveOptions(exOpts,Options)) != ""))
  925.     {
  926.     printf "%s: Error: %s\n",Name,OptErr > "/dev/stderr"
  927.     Err = 1
  928.     exit 1
  929.     }
  930.     return ArgsLeft
  931. }
  932.  
  933. # If this is not gawk, convert -x options in a help message to +x
  934. # If whitespace is non-null, it is the set of characters that may precede an
  935. # option indicator to indicate that is such.  The default is newline, space,
  936. # or tab.
  937. function mangleHelp(message,whitespace,  i,w) {
  938.     if (IGNORECASE "" != "0") {
  939.     if (whitespace == "")
  940.         whitespace = " \t\n"
  941.     for (i = 1; (w = substr(whitespace,i,1)) != ""; i++)
  942.         gsub("\\" w "-",w "+",message)
  943.     }
  944.     return message
  945. }
  946.  
  947. # ReadConfFile(): Read a file containing var/value assignments, in the form
  948. # <variable-name><assignment-char><value>.
  949. # Whitespace (spaces and tabs) around a variable (leading whitespace on the
  950. # line and whitespace between the variable name and the assignment character) 
  951. # is stripped.  Lines that do not contain an assignment operator or which
  952. # contain a null variable name are ignored, other than possibly being noted in
  953. # the return value.  If more than one assignment is made to a variable, the
  954. # first assignment is used.
  955. # Input variables:
  956. # File is the file to read.
  957. # Comment is the line-comment character.  If it is found as the first non-
  958. #     whitespace character on a line, the line is ignored.
  959. # Assign is the assignment string.  The first instance of Assign on a line
  960. #     separates the variable name from its value.
  961. # If StripWhite is true, whitespace around the value (whitespace between the
  962. #     assignment char and trailing whitespace on the line) is stripped.
  963. # VarPat is a pattern that variable names must match.  
  964. #     Example: "^[a-zA-Z][a-zA-Z0-9]+$"
  965. # If FlagsOK is true, variables are allowed to be "set" by being put alone on
  966. #     a line; no assignment operator is needed.  These variables are set in
  967. #     the output array with a null value.  Lines containing nothing but
  968. #     whitespace are still ignored.
  969. # Output variables:
  970. # Values[] contains the assignments, with the indexes being the variable names
  971. #     and the values being the assigned values.
  972. # Lines[] contains the line number that each variable occured on.  A flag set
  973. #     is record by giving it an index in Lines[] but not in Values[].
  974. # Return value:
  975. # If any errors occur, a string consisting of descriptions of the errors
  976. # separated by newlines is returned.  In no case will the string start with a
  977. # numeric value.  If no errors occur,  the number of lines read is returned.
  978. function ReadConfigFile(Values,Lines,File,Comment,Assign,StripWhite,VarPat,
  979. FlagsOK,
  980. Line,Status,Errs,AssignLen,LineNum,Var,Val) {
  981.     if (Comment != "")
  982.     Comment = "^" Comment
  983.     AssignLen = length(Assign)
  984.     if (VarPat == "")
  985.     VarPat = "."    # null varname not allowed
  986.     while ((Status = (getline Line < File)) == 1) {
  987.     LineNum++
  988.     sub("^[ \t]+","",Line)
  989.     if (Line == "")        # blank line
  990.         continue
  991.     if (Comment != "" && Line ~ Comment)
  992.         continue
  993.     if (Pos = index(Line,Assign)) {
  994.         Var = substr(Line,1,Pos-1)
  995.         Val = substr(Line,Pos+AssignLen)
  996.         if (StripWhite) {
  997.         sub("^[ \t]+","",Val)
  998.         sub("[ \t]+$","",Val)
  999.         }
  1000.     }
  1001.     else {
  1002.         Var = Line    # If no value, var is entire line
  1003.         Val = ""
  1004.     }
  1005.     if (!FlagsOK && Val == "") {
  1006.         Errs = Errs \
  1007.         sprintf("\nBad assignment on line %d of file %s: %s",
  1008.         LineNum,File,Line)
  1009.         continue
  1010.     }
  1011.     sub("[ \t]+$","",Var)
  1012.     if (Var !~ VarPat) {
  1013.         Errs = Errs sprintf("\nBad variable name on line %d of file %s: %s",
  1014.         LineNum,File,Var)
  1015.         continue
  1016.     }
  1017.     if (!(Var in Lines)) {
  1018.         Lines[Var] = LineNum
  1019.         if (Pos)
  1020.         Values[Var] = Val
  1021.     }
  1022.     }
  1023.     if (Status)
  1024.     Errs = Errs "\nCould not read file " File
  1025.     close(File)
  1026.     return Errs == "" ? LineNum : substr(Errs,2)    # Skip first newline
  1027. }
  1028.  
  1029. # Variables:
  1030. # Data is stored in Options[].
  1031. # rcFiles, OptList, VarNames, and EnvSearch are as as described for Opts().
  1032. # Global vars:
  1033. # Sets OptErr.  Uses ENVIRON[].
  1034. # If anything is read from any of the rcfiles, sets READ_RCFILE to 1.
  1035. function InitOpts(rcFiles,Options,OptList,VarNames,EnvSearch,
  1036. Line,Var,Pos,Vars,Map,CharOpt,NumVars,TypesInd,Types,Type,Ret,i,rcFile,
  1037. fNames,numrcFiles,filesRead,Err,Values,retStr) {
  1038.     split("",filesRead,"")    # make awk know this is an array
  1039.     NumVars = split(VarNames,Vars,",")
  1040.     TypesInd = Ret = 0
  1041.     if (EnvSearch == -1)
  1042.     EnvSearch = NumVars
  1043.     for (i = 1; i <= NumVars; i++) {
  1044.     Var = Vars[i]
  1045.     CharOpt = substr(OptList,++TypesInd,1)
  1046.     if (CharOpt ~ "^[:;*()#<>&]$")
  1047.         CharOpt = substr(OptList,++TypesInd,1)
  1048.     Map[Var] = CharOpt
  1049.     Types[Var] = Type = substr(OptList,TypesInd+1,1)
  1050.     # Do not overwrite entries from environment
  1051.     if (i <= EnvSearch && Var in ENVIRON &&
  1052.     (Err = AssignVal(CharOpt,ENVIRON[Var],Options,Type,1,Var,"e")) < 0)
  1053.         return Err
  1054.     }
  1055.  
  1056.     numrcFiles = split(rcFiles,fNames,":")
  1057.     for (i = 1; i <= numrcFiles; i++) {
  1058.     rcFile = fNames[i]
  1059.     if (rcFile ~ "^~/")
  1060.         rcFile = ENVIRON["HOME"] substr(rcFile,2)
  1061.     else if (rcFile ~ /^\$/) {
  1062.         rcFile = substr(rcFile,2)
  1063.         match(rcFile,"^[a-zA-Z0-9_]*")
  1064.         envvar = substr(rcFile,1,RLENGTH)
  1065.         if (envvar in ENVIRON)
  1066.         rcFile = ENVIRON[envvar] substr(rcFile,RLENGTH+1)
  1067.         else
  1068.         continue
  1069.     }
  1070.     if (rcFile in filesRead)
  1071.         continue
  1072.     # rcfiles are liable to be given more than once, e.g. UHOME and HOME
  1073.     # may be the same
  1074.     filesRead[rcFile]
  1075.     if ("x" in Options)
  1076.         printf "Reading configuration file %s\n",rcFile > "/dev/stderr"
  1077.     retStr = ReadConfigFile(Values,Lines,rcFile,"#","=",0,"",1)
  1078.     if (retStr > 0)
  1079.         READ_RCFILE = 1
  1080.     else if (ret != "") {
  1081.         OptErr = retStr
  1082.         Ret = -1
  1083.     }
  1084.     for (Var in Lines)
  1085.         if (Var in Map) {
  1086.         if ((Err = AssignVal(Map[Var],Var in Values ? Values[Var] : "",
  1087.         Options,Types[Var],Var in Values,Var,"f")) < 0)
  1088.             return Err
  1089.         }
  1090.         else {
  1091.         OptErr = sprintf(\
  1092.         "Unknown var \"%s\" assigned to on line %d\nof file %s",Var,
  1093.         Lines[Var],rcFile)
  1094.         Ret = -1
  1095.         }
  1096.     }
  1097.  
  1098.     if ("x" in Options)
  1099.     for (Var in Map)
  1100.         if (Map[Var] in Options)
  1101.         printf "(%s) %s=%s\n",Map[Var],Var,Options[Map[Var]] > \
  1102.         "/dev/stderr"
  1103.         else
  1104.         printf "(%s) %s not set\n",Map[Var],Var > "/dev/stderr"
  1105.     return Ret
  1106. }
  1107.  
  1108. # OptSets is a semicolon-separated list of sets of option sets.
  1109. # Within a list of option sets, the option sets are separated by commas.  For
  1110. # each set of sets, if any option in one of the sets is in Options[] AND any
  1111. # option in one of the other sets is in Options[], an error string is returned.
  1112. # If no conflicts are found, nothing is returned.
  1113. # Example: if OptSets = "ab,def,g;i,j", an error will be returned due to
  1114. # the exclusions presented by the first set of sets (ab,def,g) if:
  1115. # (a or b is in Options[]) AND (d, e, or f is in Options[]) OR
  1116. # (a or b is in Options[]) AND (g is in Options) OR
  1117. # (d, e, or f is in Options[]) AND (g is in Options)
  1118. # An error will be returned due to the exclusions presented by the second set
  1119. # of sets (i,j) if: (i is in Options[]) AND (j is in Options[]).
  1120. # todo: make options given on command line unset options given in config file
  1121. # todo: that they conflict with.
  1122. function ExclusiveOptions(OptSets,Options,
  1123. Sets,SetSet,NumSets,Pos1,Pos2,Len,s1,s2,c1,c2,ErrStr,L1,L2,SetSets,NumSetSets,
  1124. SetNum,OSetNum) {
  1125.     NumSetSets = split(OptSets,SetSets,";")
  1126.     # For each set of sets...
  1127.     for (SetSet = 1; SetSet <= NumSetSets; SetSet++) {
  1128.     # NumSets is the number of sets in this set of sets.
  1129.     NumSets = split(SetSets[SetSet],Sets,",")
  1130.     # For each set in a set of sets except the last...
  1131.     for (SetNum = 1; SetNum < NumSets; SetNum++) {
  1132.         s1 = Sets[SetNum]
  1133.         L1 = length(s1)
  1134.         for (Pos1 = 1; Pos1 <= L1; Pos1++)
  1135.         # If any of the options in this set was given, check whether
  1136.         # any of the options in the other sets was given.  Only check
  1137.         # later sets since earlier sets will have already been checked
  1138.         # against this set.
  1139.         if ((c1 = substr(s1,Pos1,1)) in Options)
  1140.             for (OSetNum = SetNum+1; OSetNum <= NumSets; OSetNum++) {
  1141.             s2 = Sets[OSetNum]
  1142.             L2 = length(s2)
  1143.             for (Pos2 = 1; Pos2 <= L2; Pos2++)
  1144.                 if ((c2 = substr(s2,Pos2,1)) in Options)
  1145.                 ErrStr = ErrStr "\n"\
  1146.                 sprintf("Cannot give both %s and %s options.",
  1147.                 c1,c2)
  1148.             }
  1149.     }
  1150.     }
  1151.     if (ErrStr != "")
  1152.     return substr(ErrStr,2)
  1153.     return ""
  1154. }
  1155.  
  1156. # The value of each instance of option Opt that occurs in Options[] is made an
  1157. # index of Set[].
  1158. # The return value is the number of instances of Opt in Options.
  1159. function Opt2Set(Options,Opt,Set,  count) {
  1160.     if (!(Opt in Options))
  1161.     return 0
  1162.     Set[Options[Opt]]
  1163.     count = Options[Opt,"count"]
  1164.     for (; count > 1; count--)
  1165.     Set[Options[Opt,count]]
  1166.     return count
  1167. }
  1168.  
  1169. # The value of each instance of option Opt that occurs in Options[] that
  1170. # begins with "!" is made an index of nSet[] (with the ! stripped from it).
  1171. # Other values are made indexes of Set[].
  1172. # The return value is the number of instances of Opt in Options.
  1173. function Opt2Sets(Options,Opt,Set,nSet,  count,aSet,ret) {
  1174.     ret = Opt2Set(Options,Opt,aSet)
  1175.     for (value in aSet)
  1176.     if (substr(value,1,1) == "!")
  1177.         nSet[substr(value,2)]
  1178.     else
  1179.         Set[value]
  1180.     return ret
  1181. }
  1182.  
  1183. # Returns true if any option in the string Opts was given, as indicated by the
  1184. # data in Options[]. If any of Arg, Env, or File are true, the given opts are
  1185. # only considered to have been set if they were set in the command line
  1186. # arguments, environment, or in a configuration file, respectively. 
  1187. function OptsGiven(Options,Opts,Arg,Env,File,  l,i,Opt,j,c) {
  1188.     if (!Arg && !Env && !File)
  1189.     Arg = Env = File = 1
  1190.     l = length(Opts)
  1191.     for (i = 1; i <= l; i++) {
  1192.     Opt = substr(Opts,i,1)
  1193.     for (j = 1; (Opt,"num",j) in Options; j++) {
  1194.         c = Options[Opt,"num",j]
  1195.         if (Arg && c+0 > 0 || File && c == "f" || Env && c == "e")
  1196.         return 1
  1197.     }
  1198.     }
  1199.     return 0
  1200. }
  1201. ### End of ProcArgs library
  1202.